package cc.verywell.pureblock.msg.nfs.linkmap;

import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;

import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.log.ThreadListener;

/**
* index+pos8<A>,index+pos8<B>,index+pos8<C>
* poscount+<A>+<B>+<C>
* [last<=index && index<next] 
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年6月16日 下午3:36:05
*/
public class EasyTableHD implements EasyTable,Closeable {

	final Path file;
	final SeekableByteChannel channel;
	
	private long indexStart;
	private int[] indexPos;
	private byte[] indexPage;
	private PageBuffer[] dataBuffer;
	final private ThreadListener tl;
	public EasyTableHD(Path file,ThreadListener tl) throws IOException {
		this.tl = tl!=null?tl:ThreadListener.NONE;
		this.file = file;
		try {
			tl.threadIn();
			this.channel = //AsynchronousFileChannel.open(file, StandardOpenOption.READ);
			 Files.newByteChannel(file, StandardOpenOption.READ);
		} catch (IOException e) {
			tl.threadOut();
			throw e;
		}
		try {
			tl.threadWait();
			ByteBuffer bb = ByteBuffer.allocate(8);
			bb.order(ByteOrder.BIG_ENDIAN);
			//this.channel.read(bb, channel.size()-8).get();
			this.channel.position(channel.size()-8);
			this.channel.read(bb);
			bb.flip();
			long indexStart = bb.getLong();
			
			long l = channel.size()-8-indexStart;
			if(l<0 || l>Integer.MAX_VALUE)throw new EasyIOException("index length "+l+" > Integer.Max_VALUE");
			bb = ByteBuffer.allocate((int)l);
			//this.channel.read(bb, indexStart).get();
			this.channel.position(indexStart);
			this.channel.read(bb);
			int[] poss = new int[LinkCell.lengthDecodeInt(bb)];
			int j = 0;
			for(int i = 0;i<poss.length;i++) {
				poss[i] = j += LinkCell.lengthDecodeInt(bb);
			}
			this.indexStart = indexStart -= j;
			this.indexPos = poss;
			this.indexPage = new byte[j];
			this.dataBuffer = new PageBuffer[poss.length+1];
			//this.channel.read(ByteBuffer.wrap(this.indexPage), indexStart).get();
			this.channel.position(indexStart);
			this.channel.read(ByteBuffer.wrap(this.indexPage));
			tl.threadWake();
		} catch (IOException e) {
			close();
			throw e;
		}
	}
	@Override
	public void close(){
		autoClose().run();
	}
	public Runnable autoClose() {
		return new AutoCloseRun(channel, tl);
	}
	//必须static, 必须不能有table 任何引用
	static public class AutoCloseRun implements Runnable{
		private final SeekableByteChannel channel;
		private final ThreadListener tl;
		public AutoCloseRun(SeekableByteChannel channel, ThreadListener tl) {
			this.channel = channel;
			this.tl = tl;
		}
		@Override
		public void run() {
			try {
				synchronized (channel) {
					if(!this.channel.isOpen())return;
					this.channel.close();
					this.tl.threadOut();
				}
			} catch (IOException e) {
				if(Pblog.FS)Pblog.FS(e);
			}
		}
	}
	/**
	 * only visit from {@link #getBufferedHdPage}
	 */
	protected EasyTableHDpage getHdPage(int index) {
		tl.threadWait();
		long f,g;
		if(index==indexPos.length) {
			f = getLong(indexPage,indexPage.length-8);
			g = indexStart;
		}else {
			f = index==0?0:getLong(indexPage,indexPos[index-1]-8);
			g = getLong(indexPage,indexPos[index]-8);
		}
		boolean faild = true;
		byte[] page = new byte[(int)(g-f)];
		try {
			synchronized (channel) {
				if(channel.isOpen()) {
					channel.position(f);
					channel.read(ByteBuffer.wrap(page));
					faild = false;
				}
			}
		} catch (IOException e) {
			if(Pblog.FS)Pblog.FS(e);
		}
		if(faild) {
			try (SeekableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.READ);){
				channel.position(f);
				channel.read(ByteBuffer.wrap(page));
			}catch (IOException e) {
				tl.threadWake();
				throw new EasyIOException(e);
			}
		}
		tl.threadWake();
		return new EasyTableHDpage(page);
	}
	/**
	 * only visit from {@link EasyTableHDIterator#resetIdx}
	 */
	protected EasyTableHDpage getBufferedHdPage(int index) {
		PageBuffer pagebuffer;
		synchronized (dataBuffer) {
			pagebuffer = dataBuffer[index];
			if(pagebuffer==null) {
				dataBuffer[index] = pagebuffer = new PageBuffer();
			}
		}
		EasyTableHDpage hdpage = pagebuffer.get();
		if(hdpage==null) {
			synchronized (hdpage) {
				hdpage = pagebuffer.get();
				if(hdpage==null) {
					hdpage = getHdPage(index);
					pagebuffer.set(hdpage);
				}
			}
		}
		return hdpage;
	}

	@Override
	public EasyIterator get(LinkCell startWith) {
		int a=0,b=indexPos.length;
		while(a<b) {
			int c = (a+b)>>1;
			if(IndexSmallerThan(c,startWith))
				a=c+1;
			else
				b=c;
		}
		EasyTableHDIterator it = new EasyTableHDIterator(a,startWith);
		if(it.hasNext())return it;
		return null;
	}
	/**
	 * only visit from {@link #get}
	 */
	private boolean IndexSmallerThan(final int c,final LinkCell startWith) {
		int f = c==0?0:indexPos[c-1];
		int g = indexPos[c];
		return startWith.compareTo(indexPage, f, g-f-8)>0;
	}
	/**
	 * only visit from {@link EasyTableHDIterator#next}
	 */
	private boolean IndexStartWith(final int c,final LinkCell startWith) {
		int f = c==0?0:indexPos[c-1];
		int g = indexPos[c];
		int l = g-f-8;
		if(l<startWith.encoded.length)return false;
		l=startWith.encoded.length;
		return 0==startWith.compareTo(indexPage, f, l);
	}
	
	private class EasyTableHDIterator implements EasyIterator{

		protected EasyIterator pageit;
		protected int idx;
		protected LinkCell startWith;
		public EasyTableHDIterator(int idx,LinkCell startWith) {
			this.idx = idx;
			this.startWith = startWith;
			resetIdx(idx);
			
		}
		private void resetIdx(int a) {
			pageit = getBufferedHdPage(a).get(startWith);
		}

		@Override
		public boolean hasNext() {
			return pageit!=null;
		}

		@Override
		public LinkCell next() {
			if(pageit==null)return null;
			LinkCell cr = pageit.next();
			if(!pageit.hasNext()) {
				pageit = null;
				if(idx<indexPos.length && IndexStartWith(idx, startWith)) {
					resetIdx(++idx);
				}
			}
			return cr;
		}

		@Override
		public LinkCell current() {
			return pageit!=null?pageit.current():null;
		}
	}
	
	
	static public EasyTableHDhead add(int grade,long fileNumber,Path file,EasyIterator it,EasyCompactSizeLimit accept) {
		if(!it.hasNext())throw new EasyIOException(file+" no data to write");
		try(
			SeekableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.CREATE , StandardOpenOption.WRITE)//AsynchronousFileChannel.open(file, StandardOpenOption.CREATE_NEW , StandardOpenOption.APPEND);
		){
			if(accept!=null)accept.newFile();
			ArrayList<IndexNode> al = new ArrayList<IndexNode>();
			long pos = 0;
			long idxSize = 0;
//			long lastSize = 8;
			LinkCell smallest = it.current();
			
			ByteBuffer bb = ByteBuffer.allocate(Math.max(4*1024, smallest.encoded.length+10));
			
			LinkCell last = EasyTableHDpage.add(bb, it);
			bb.flip();
			channel.write(bb);//, pos).get();
			pos += bb.position();
			while(it.hasNext()) {
				if(accept!=null && accept.sizeLimit(pos+idxSize))break;
				LinkCell next = it.current();
				IndexNode in = new IndexNode(getSepIndex(last, next),pos);
				al.add(in);
				idxSize+=in.size();
//				if(idxSize!=0)lastSize+=LinkCell.lengthSize(idxSize);
				
				if(bb.capacity()<next.encoded.length+10) {
					bb = ByteBuffer.allocate(next.encoded.length+10);
				}else {
					bb.clear();
				}
				last = EasyTableHDpage.add(bb, it);
				bb.flip();
				channel.write(bb);//, pos).get();
				pos += bb.position();
			}
			bb.clear();
			bb.order(ByteOrder.BIG_ENDIAN);
			
			for (IndexNode in : al) {
				if(bb.remaining()<in.size()) {
					bb.flip();
					channel.write(bb);//, pos).get();
					pos += bb.position();
					bb.clear();
				}
				bb.put(in.idx);
				bb.putLong(in.posision);
			}
			bb.flip();
			channel.write(bb);//, pos).get();
			pos += bb.position();
			bb.clear();
			
			long startIndex = pos;
			
			LinkCell.lengthEncodeInt(bb,al.size());
			for (IndexNode in : al) {
				if(bb.remaining()<5) {
					bb.flip();
					channel.write(bb);//, pos).get();
					pos += bb.position();
					bb.clear();
				}
				LinkCell.lengthEncodeInt(bb,in.size());
			}
			bb.flip();
			channel.write(bb);//, pos).get();
			pos += bb.position();
			bb.clear();
			
			bb.putLong(startIndex);
			bb.flip();
			channel.write(bb);//, pos).get();
			pos += bb.position();

			//channel.force(true);
			channel.truncate(pos).close();
			
			return new EasyTableHDhead(grade,fileNumber,file, smallest, last);
		} catch (IOException e) {
			try {
				Files.delete(file);
			} catch (IOException e1) {
				if(Pblog.FS)Pblog.FS(e1);
			}
			throw new EasyIOException(e);
		}
	}
	static public class IndexNode{
		public byte[] idx;
		public long posision;
		public IndexNode(byte[] idx, long posision) {
			super();
			this.idx = idx;
			this.posision = posision;
		}
		public int size() {
			return idx.length+8;
		}
	};
	
	
	static private class PageBuffer{
		private WeakReference<EasyTableHDpage> page;
		public EasyTableHDpage get() {
			return page==null?null:page.get();
		}
		public void set(EasyTableHDpage page) {
			this.page = new WeakReference<EasyTableHDpage>(page);
		}
	}
	
	/**
	 * @param before
	 * @param next
	 * @return before<=返回一个这样的数<next;
	 */
	static byte[] getSepIndex(LinkCell before,LinkCell next) {
        byte[] m = before.encoded,u = next.encoded;

        int l = Math.min(m.length, u.length);
        for (int i = 0; i < l; i++) {
            if (m[i] != u[i]) {
            	if(i==l-1)break;
            	
                int mi = 0xFF & m[i];
                int ui = 0xFF & u[i];
               
                if(mi+1<ui) {
                }else {
                	aa:{
                		final byte MAXBYTE = (byte)0xFF; 
                		for(i++;i<l;i++) {
                			if(m[i]!=MAXBYTE) {
                				break aa;
                			}
                		}
                		return m;
                	}
                }
            	byte[] x = Arrays.copyOf(m, i+1);
            	x[i] += 1; 
                return x;
            }
        }
        return m;
	}
	static long getLong(byte[] bb,int offset) {
		long r = (0xFFL & bb[offset++])<<56;
		r |= (0xFFL & bb[offset++])<<48;
		r |= (0xFFL & bb[offset++])<<40;
		r |= (0xFFL & bb[offset++])<<32;
		r |= (0xFFL & bb[offset++])<<24;
		r |= (0xFFL & bb[offset++])<<16;
		r |= (0xFFL & bb[offset++])<<8;
		r |= (0xFFL & bb[offset]);
		return r;
	}

}
